﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace VehicleTrajectoryApp
{
    public class Path 
    {
        public VehicleTrajectoryType type;
        public List<int> nodeList;
        public GPSLink[] gpsLinkArray;
        public float path_distance_in_mile;
        public Path(VehicleTrajectoryType trajectory_type, List<int> nodes, float distance_in_mile)
        {
            this.path_distance_in_mile = distance_in_mile;
            type = trajectory_type;            
            nodeList = nodes;
        }

        public Path(VehicleTrajectoryType trajectory_type, GPSLink[] gpsLinkArray, float distance_in_mile)
        {
            this.path_distance_in_mile = distance_in_mile;
            this.type = trajectory_type;
            this.gpsLinkArray = gpsLinkArray;
        }

        public bool IsEquals(Path path)
        {
            if (this == path)
            {
                return true;
            }

            if (this.type != path.type)
            {
                return false;
            }

            bool isDifferent = false;
            if (this.nodeList != null && path.nodeList != null)
            {
                if (this.nodeList.Count == path.nodeList.Count)
                {
                    for (int n = 0; n < this.nodeList.Count; n++)
                    {
                        if (this.nodeList[n] != path.nodeList[n])
                        {
                            isDifferent = true;
                            break;
                        }
                    }
                    if (isDifferent)
                    {
                        return false;
                    }
                    else
                    {
                        return true;
                    }
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        public string ConvertVehicleTraceToKMLString(string name, string colorCode = "ffff0000")
        {
            string s = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
            s += "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n";
            s += "<Document>";
            s += "<name>" + name + "</name>\n";
            s += "<Placemark>\n";
            s += "<name>" + name + "</name>\n";
            s += "<Style><LineStyle><width>4</width><gx:labelVisibility>1</gx:labelVisibility><color>";
            //s += "ffff0000";
            s += colorCode;
            s += "</color></LineStyle><IconStyle><scale>0</scale></IconStyle><PolyStyle><fill>0</fill></PolyStyle></Style>\n";
            s += "<MultiGeometry>";
            s += "<LineString>\n";
            s += "<extrude>0</extrude>\n";
            s += "<tessellate>0</tessellate>\n";
            s += "<altitudeMode>relativeToGround</altitudeMode>\n";
            s += "<coordinates>\n";

            for (int n = 0; n < gpsLinkArray.Length; n++)
            {
                s += String.Format("{0},{1} {2},{3} ", gpsLinkArray[n].x1, gpsLinkArray[n].y1, gpsLinkArray[n].x2, gpsLinkArray[n].y2);
            }

            s += "</coordinates>\n";
            s += "</LineString>\n";
            s += "<Point>";
            s += "<coordinates>";
            s += String.Format("{0},{1},0\n", gpsLinkArray[0].x1, gpsLinkArray[0].y1);
            s += "</coordinates>";
            s += "</Point>\n";
            s += "</MultiGeometry>\n";
            s += "</Placemark>\n";
            s += "</Document>";
            s += "</kml>";

            return s;
        }
    }

    public class ODPathSummary
    {
        public List<Path> pathList = new List<Path>();
        private int TotalCounts = 0;

        //Key: path index as in pathList, Value: counts of path
        public Dictionary<int, int>[] ScenarioBasedPathCounts;
        
        //Key: path index as in pathList, Value: summary of travel time
        public Dictionary<int, List<float>>[] ScenarioBasedExperiencedPathTravelTimeMap;
        public int num_of_scenarios;

        public ODPathSummary(int num_of_scenarios=3)
        {
            this.num_of_scenarios = num_of_scenarios;
            ScenarioBasedExperiencedPathTravelTimeMap = new Dictionary<int, List<float>>[num_of_scenarios];
            ScenarioBasedPathCounts = new Dictionary<int, int>[num_of_scenarios];

            for (int scenario = 0; scenario < num_of_scenarios; scenario++)
            {
                ScenarioBasedPathCounts[scenario] = new Dictionary<int, int>();
                ScenarioBasedExperiencedPathTravelTimeMap[scenario] = new Dictionary<int, List<float>>();
            }
        }


        private int ContainPath(Path path)
        {
            if (pathList == null)
            {
                return -1;
            }
            else
            {
                int index = -1;
                for (int n = 0; n < pathList.Count; n++)
                {
                    if (path.IsEquals(pathList[n]))
                    {
                        index = n;
                        break;
                    }
                }

                return index;
            }
        }

        public void AddPathByScenario(int scenario, float path_travel_time, Path path)
        {
            if (scenario >= 0 && scenario < num_of_scenarios)
            {                
                TotalCounts++;
                int pathIndex = ContainPath(path);
                if (pathIndex == -1)
                {
                    pathIndex = pathList.Count;
                    pathList.Add(path);

                    if (!ScenarioBasedPathCounts[scenario].ContainsKey(pathIndex))
                    {
                        ScenarioBasedPathCounts[scenario][pathIndex] = 0;
                    }
                    ScenarioBasedPathCounts[scenario][pathIndex]++;

                    if (!ScenarioBasedExperiencedPathTravelTimeMap[scenario].ContainsKey(pathIndex))
                    {
                        ScenarioBasedExperiencedPathTravelTimeMap[scenario][pathIndex] = new List<float>();
                    }
                    ScenarioBasedExperiencedPathTravelTimeMap[scenario][pathIndex].Add(path_travel_time);
                }
                else
                {
                    if (!ScenarioBasedPathCounts[scenario].ContainsKey(pathIndex))
                    {
                        ScenarioBasedPathCounts[scenario][pathIndex] = 0;
                    }
                    ScenarioBasedPathCounts[scenario][pathIndex]++;

                    if (!ScenarioBasedExperiencedPathTravelTimeMap[scenario].ContainsKey(pathIndex))
                    {
                        ScenarioBasedExperiencedPathTravelTimeMap[scenario][pathIndex] = new List<float>();
                    }
                    ScenarioBasedExperiencedPathTravelTimeMap[scenario][pathIndex].Add(path_travel_time);
                }
            }
        }

        private float CalculateSTD(List<float> values)
        {
            float std = 0.0f;
            if (values.Count > 0)
            {
                float average = values.Average();
                float sum = values.Sum(d => (d - average) * (d - average));
                if (values.Count > 1)
                {
                    std = (float)Math.Sqrt(sum / (values.Count - 1));
                }
            }
            return std;
        }

        public void GetGeneralSummary(out int counts, out float average, out float std, out float max_travel_time)
        {
            counts = 0;
            average = 0;
            std = 0;
            max_travel_time = float.MinValue;

            List<float> travelTimeList = new List<float>();

            for (int scenario = 0; scenario < num_of_scenarios; scenario++)
            {
                for (int pathIndex = 0; pathIndex < pathList.Count; pathIndex++)
                {
                    if (ScenarioBasedPathCounts[scenario].ContainsKey(pathIndex) && ScenarioBasedExperiencedPathTravelTimeMap[scenario].ContainsKey(pathIndex))
                    {
                        counts += ScenarioBasedPathCounts[scenario][pathIndex];
                        travelTimeList.AddRange(ScenarioBasedExperiencedPathTravelTimeMap[scenario][pathIndex]);
                    }
                }
            }

            average = travelTimeList.Count > 0 ? travelTimeList.Average() : 0.0f;
            max_travel_time = travelTimeList.Count > 0 ? travelTimeList.Max() : 0.0f;
            std = CalculateSTD(travelTimeList);
        }

        public void GetSummaryByScenario(int scenario, out int counts, out float average, out float std)
        {
            counts = 0;
            average = 0.0F;
            std = 0.0F;

            List<float> travelTimeList = new List<float>();

            if (scenario >= 0 && scenario < num_of_scenarios)
            {
                for (int pathIndex = 0; pathIndex < pathList.Count; pathIndex++)
                {
                    if (ScenarioBasedPathCounts[scenario].ContainsKey(pathIndex) && ScenarioBasedExperiencedPathTravelTimeMap[scenario].ContainsKey(pathIndex))
                    {
                        counts += ScenarioBasedPathCounts[scenario][pathIndex];
                        travelTimeList.AddRange(ScenarioBasedExperiencedPathTravelTimeMap[scenario][pathIndex]);
                    }
                }

                average = travelTimeList.Count > 0 ? travelTimeList.Average() : 0.0f;
                std = CalculateSTD(travelTimeList);
            }
        }

        public void GetSummaryByScenarioAndPathIndex(int scenario, int pathIndex, out int counts, out float average, out float std, out float average_speed, out float speed_std)
        {
            counts = 0;
            average = 0.0F;
            std = 0.0F;

            float distance = this.pathList[pathIndex].path_distance_in_mile;

            average_speed = speed_std = 0.0f;

            List<float> travelTimeList = new List<float>();
            List<float> speedList = new List<float>();

            if (scenario >= 0 && scenario < num_of_scenarios)
            {
                if (ScenarioBasedPathCounts[scenario].ContainsKey(pathIndex) && ScenarioBasedExperiencedPathTravelTimeMap[scenario].ContainsKey(pathIndex))
                {
                    counts += ScenarioBasedPathCounts[scenario][pathIndex];
                    travelTimeList.AddRange(ScenarioBasedExperiencedPathTravelTimeMap[scenario][pathIndex]);
                }


                average = travelTimeList.Count > 0 ? travelTimeList.Average() : 0.0f;
                std = CalculateSTD(travelTimeList);

                for (int i = 0; i < travelTimeList.Count; i++)
                {
                    speedList.Add(distance / travelTimeList[i] * 60.0f);
                }
                average_speed = speedList.Count > 0 ? speedList.Average() : 0.0f;
                speed_std = CalculateSTD(speedList);
            }
        }

        public void GetSummaryByPathIndex(int pathIndex, out int counts, out float average, out float std)
        {
            counts = 0;
            average = 0.0F;
            std = 0.0F;

            List<float> travelTimeList = new List<float>();

            if (pathIndex == -1)
            {
                for (int scenario = 0; scenario < num_of_scenarios; scenario++)
                {
                    for (int pIdx = 0; pIdx < pathList.Count; pIdx++)
                    {
                        if (ScenarioBasedPathCounts[scenario].ContainsKey(pIdx) && ScenarioBasedExperiencedPathTravelTimeMap[scenario].ContainsKey(pIdx))
                        {
                            counts += ScenarioBasedPathCounts[scenario][pIdx];
                            travelTimeList.AddRange(ScenarioBasedExperiencedPathTravelTimeMap[scenario][pathIndex]);
                        }
                    }
                }
            }
            else
            {
                for (int scenario = 0; scenario < num_of_scenarios; scenario++)
                {
                    if (ScenarioBasedPathCounts[scenario].ContainsKey(pathIndex) && ScenarioBasedExperiencedPathTravelTimeMap[scenario].ContainsKey(pathIndex))
                    {
                        counts += ScenarioBasedPathCounts[scenario][pathIndex];
                        travelTimeList.AddRange(ScenarioBasedExperiencedPathTravelTimeMap[scenario][pathIndex]);
                    }
                }
            }

            average = travelTimeList.Count > 0 ? travelTimeList.Average() : 0.0f;
            std = CalculateSTD(travelTimeList);
        }
    }

    public class CriticalOD
    {
        public int totalCounts = 0;
        public int num_of_scenarios = 0;
        public Dictionary<int, ODPathSummary> odSummaryMap = new Dictionary<int, ODPathSummary>();

        public CriticalOD(int num_of_scenarios = 3)
        {
            this.num_of_scenarios = num_of_scenarios;
        }

        public void AddPath(int departure_hour, int scenario, float path_travel_time, Path path)
        {
            if (!odSummaryMap.ContainsKey(departure_hour))
            {
                odSummaryMap[departure_hour] = new ODPathSummary(this.num_of_scenarios);
            }

            odSummaryMap[departure_hour].AddPathByScenario(scenario, path_travel_time, path);
            totalCounts++;
        }
    }
}
